/*
 * Copyright 2000 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "WtUtil.h"
#include "WtTable.h"

const char *wtBadUsagePrefix = "Usage: ";
const char *wtBadUsagePrefix2 = "Usage:\n\n";

char *WtToString(Tcl_Obj *obj)
{
  return Tcl_GetString(obj);
}

Tcl_Obj *WtNewString(const char *val)
{
  return Tcl_NewStringObj(val ? val : NULL, val ? -1 : 0);
}

Tcl_Obj *WtNewBool(int val)
{
  return Tcl_NewBooleanObj(val);
}

/* Update a WtTable from an apache table */

int WtUpdateTableFromAp(Tcl_Obj *tbl, table *apTable,
    Tcl_Interp *interp)
{
  return WtUpdateTableObjFromAp(tbl, apTable, interp, 1);
}

int WtUpdateTableFromApNoMulti(Tcl_Obj *tbl, table *apTable,
    Tcl_Interp *interp)
{
  return WtUpdateTableObjFromAp(tbl, apTable, interp, 0);
}

int WtUpdateTableObjFromAp(Tcl_Obj *tbl, table *apTable,
    Tcl_Interp *interp, int doMulti)
{
  int ok = 1, i;
  array_header *arr;
  table_entry *elts;
  char *key, *val;
  Tcl_Obj *keyObj, *valObj;

  arr = ap_table_elts(apTable);
  elts = (table_entry *)arr->elts;

  for (i = 0; i < arr->nelts; i++) {
    key = elts[i].key ? elts[i].key : "";
    val = elts[i].val ? elts[i].val : "";

    keyObj = Tcl_NewStringObj(key, -1);
    Tcl_IncrRefCount(keyObj);

    valObj = Tcl_NewStringObj(val, -1);
    Tcl_IncrRefCount(valObj);

    if (doMulti) {
      ok = WtTableAppendToList(tbl, keyObj, valObj, NULL, interp);
    } else {
      WtTableSet(tbl, keyObj, valObj);
    }

    Tcl_DecrRefCount(keyObj);
    Tcl_DecrRefCount(valObj);

    if (!ok) {
      break;
    }
  }

  return ok;
}

/* Update an apache table from a WtTable */

int WtUpdateApFromLVTable(table *apTable, Tcl_Obj *tbl,
    Tcl_Interp *interp)
{
  Tcl_HashTable *hashTable;
  Tcl_HashEntry *ent;
  Tcl_HashSearch search;
  Tcl_Obj *list, *obj;
  char *key;
  int ok = 1, i, len;

  hashTable = WtGetTableMap(tbl);

  ent = Tcl_FirstHashEntry(hashTable, &search);

  while (ent && ok) {
    list = (Tcl_Obj *)Tcl_GetHashValue(ent);
    if (list) {
      if (Tcl_ListObjLength(interp, list, &len) != TCL_OK) {
        ok = 0;
        break;
      }

      key = WtToString((Tcl_Obj *)Tcl_GetHashKey(hashTable, ent));
      ap_table_unset(apTable, key);

      for (i = 0; i < len; i++) {
        if (Tcl_ListObjIndex(interp, list, 0, &obj) != TCL_OK) {
          ok = 0;
          break;
        } else {
          ap_table_add(apTable, key, WtToString(obj));
        }
      }
    }
    ent = Tcl_NextHashEntry(&search);
  }

  return ok;
}

int WtLog(const char *file, int line, int mask, WtContext *w,
    const char *fmt, ...)
{
  va_list args;
  int ok = 0;
  char buf[MAX_STRING_LEN];

  va_start(args, fmt);
  if (ap_vsnprintf(buf, sizeof(buf), fmt, args)) {
    ok = WtLogInternal(file, line, mask, w, buf, 1);
  }
  va_end(args);

  return ok;
}

int WtLogInternal(const char *file, int line, int mask, WtContext *w,
    const char *str, int addNotice)
{
  int ok = 1;
  char buf[MAX_STRING_LEN];

  memcpy(buf, str, MAX_STRING_LEN);
  // XXX: Convert newlines to spaces

  if (w->apReq) {
    ap_log_rerror(file, line, mask, w->apReq, "%s", buf);
  } else {
    ap_log_error(file, line, mask, w->apServer, "%s", buf);
  }

  if ((mask & APLOG_LEVELMASK) <= APLOG_ERR) {
    w->web->errorState = 1;
  }

  if (addNotice && w->web && w->web->interp && w->web->notices &&
      !WtAddNotice(w, buf, mask & APLOG_LEVELMASK, w->web->interp)) {
    ok = 0;
  }

  return ok;
}

int WtAddNotice(WtContext *w, char *notice, int mask, Tcl_Interp *interp)
{
  int ok = 1, level = mask & APLOG_LEVELMASK;
  Tcl_Obj *objv[2], *item;

  objv[0] = Tcl_NewIntObj(level);
  objv[1] = WtNewString(notice);
  item = Tcl_NewListObj(2, objv);
  Tcl_IncrRefCount(item);

  WtCopyOnWrite(&w->web->notices);

  if (Tcl_ListObjAppendElement(interp, w->web->notices, item) != TCL_OK) {
    if (interp) {
      WtLogInternal(HERE, APLOG_ERR | APLOG_NOERRNO, w,
        (interp ? Tcl_GetStringResult(interp) : "Failed to add notice."), 0);
    }
    ok = 0;
  }

  Tcl_DecrRefCount(item);

  return ok;
}

int WtCopyOnWrite(Tcl_Obj **objPtr)
{
  Tcl_Obj *t;

  if (*objPtr && Tcl_IsShared(*objPtr)) {
    t = Tcl_DuplicateObj(*objPtr);
    Tcl_IncrRefCount(t);
    Tcl_DecrRefCount(*objPtr);
    *objPtr = t;
    return 1;
  }
  return 0;
}

int WtEvalIncr(Tcl_Interp *interp, int objc, Tcl_Obj **objv, int flags)
{
  int i, rc;
  for (i = 0; i < objc; i++) {
    Tcl_IncrRefCount(objv[i]);
  }
  rc = Tcl_EvalObjv(interp, objc, objv, flags);
  for (i = 0; i < objc; i++) {
    Tcl_DecrRefCount(objv[i]);
  }
  return rc;
}

Tcl_Obj *WtIncr(Tcl_Obj *obj)
{
  if (obj) {
    Tcl_IncrRefCount(obj);
  }
  return obj;
}

Tcl_Obj *WtDecr(Tcl_Obj *obj)
{
  if (obj) {
    Tcl_DecrRefCount(obj);
  }
  return obj;
}

Tcl_Obj **WtIncrList(Tcl_Obj **objv, int n)
{
  int i;
  for (i = 0; i < n; i++) {
    Tcl_IncrRefCount(objv[i]);
  }
  return objv;
}

Tcl_Obj **WtDecrList(Tcl_Obj **objv, int n)
{
  int i;
  for (i = 0; i < n; i++) {
    Tcl_DecrRefCount(objv[i]);
  }
  return objv;
}

Tcl_Obj *WtSetObj(Tcl_Obj **objPtr, Tcl_Obj *val)
{
  if (*objPtr) {
    Tcl_DecrRefCount(*objPtr);
  }
  if (*objPtr = val) {
    Tcl_IncrRefCount(*objPtr);
  }
  return *objPtr;
}

int WtGetBoolResult(Tcl_Interp *interp, int *boolVal)
{
  int ok = 1;
  Tcl_Obj *result = Tcl_GetObjResult(interp);

  if (!result) {
    Tcl_AppendResult(interp, "Failed to get result.");
    ok = 0;
  } else {
    Tcl_IncrRefCount(result);
    Tcl_ResetResult(interp);
    if (Tcl_GetBooleanFromObj(interp, result, boolVal) != TCL_OK) {
      ok = 0;
    }
    Tcl_DecrRefCount(result);
  }

  return ok;
}

int WtCommandExists(Tcl_Interp *interp, const char *name)
{
  Tcl_CmdInfo info;
  return Tcl_GetCommandInfo(interp, name, &info) != 0;
}

char *WtSafeStr(const char *str)
{
  return str ? (char *)str : "";
}

/* Get a typed value pass to a command */

Tcl_Obj *WtCmdGetVal(Tcl_Obj *val, Tcl_ObjType *type,
    Tcl_Interp *interp)
{
  if (Tcl_ConvertToType(interp, val, type) == TCL_OK) {
    return val;
  }
  return NULL;
}

/* Get a variable passed to a command */

int WtCmdGetVar(Tcl_Obj *var, Tcl_Obj **val, Tcl_ObjType *type,
    Tcl_Interp *interp)
{
  int ok = 0;
  *val = Tcl_ObjGetVar2(interp, var, NULL, 0);
  if (*val) {
    if ((*val)->typePtr == type ||
        Tcl_ConvertToType(interp, *val, type)) {
      ok = 1;
    }
  }
  return ok;
}

/* Get and unshare a variable passed to a command */

int WtCmdOwnVar(Tcl_Obj *var, Tcl_Obj **val, Tcl_ObjType *type,
    int *created, Tcl_Interp *interp)
{
  int ok = 1;
  Tcl_Obj *obj;

  *val = NULL;
  *created = 0;

  obj = Tcl_ObjGetVar2(interp, var, NULL, 0);

  if (obj) {
    if (Tcl_IsShared(obj)) {
      if (!(obj = Tcl_DuplicateObj(obj))) {
        ok = 0;
      } else {
        Tcl_IncrRefCount(obj);
        if (!Tcl_ConvertToType(interp, obj, type)) {
          Tcl_DecrRefCount(obj);
          ok = 0;
        } else {
          *val = obj;
          *created = 1;
        }
      }
    } else if (Tcl_ConvertToType(interp, obj, type) != TCL_OK) {
      ok = 0;
    } else {
      *val = obj;
    }
  }

  return ok;
}

/* Reset a var */

Tcl_Obj *WtCmdSetVar(Tcl_Obj *var, Tcl_Obj *val, Tcl_Interp *interp)
{
  Tcl_Obj *ret;
  Tcl_IncrRefCount(val);
  ret = Tcl_ObjSetVar2(interp, var, NULL, val, TCL_LEAVE_ERR_MSG);
  Tcl_DecrRefCount(val);
  return ret;
}

void WtUpdateProtectedObjString(Tcl_Obj *obj)
{
  /* Empty string. Do not attempt to serialize. */
  obj->bytes = Tcl_Alloc(1);
  obj->bytes[0] = '\0';
  obj->length = 1;
}

int WtSetProtectedObjFromAny(Tcl_Interp *interp, Tcl_Obj *obj,
    Tcl_ObjType *type)
{
  if (obj->typePtr != type) {
    Tcl_AppendResult(interp, "Conversion to object is not permitted.");
    return TCL_ERROR;
  }
  return TCL_OK;
}

unsigned int WtHashString(const char *str, int len)
{
  int i;
  unsigned int result = 0;
  const char *p;

  if (len != -1) {
    for (i = 0; i < len; i++) {
      result += (result << 3) + str[i];
    }
  } else {
    for (p = str; *p; p++) {
      result += (result << 3) + *p;
    }
  }

  return result;
}

/* Code bits for URL encoding taken from PHP source */

static const char *hexDigits = "0123456789ABCDEF";

char *WtEncodeUrl(const char *str, int stringLen, int *resultLen)
{
  const char *p, *end = str + stringLen;
  char *q, *dest = ckalloc(stringLen * 3 + 1);
  char c;

  q = dest;

  for (p = str; p < end; p++) {
    c = *p;
    if (c == ' ') {
      *q++ = '+';
    } else if ((c < '0' && c != '-' && c != '.') ||
        (c < 'A' && c > '9') ||
        (c > 'Z' && c < 'a' && c != '_') || (c > 'z')) {
      *q = '%';
      q[1] = hexDigits[c >> 4];
      q[2] = hexDigits[c & 0x0f];
      q += 3;
    } else {
      *q++ = c;
    }
  }

  *q = '\0';
  if (resultLen) {
    *resultLen = q - dest;
  }

  return dest;
}

int WtHexToChar(char *p)
{
  int val, c;

  c = ((unsigned char *)p)[0];
  if (isupper(c)) {
    c = tolower(c);
  }
  val = (c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10) * 16;
  c = ((unsigned char *)p)[1];
  if (isupper(c)) {
    c = tolower(c);
  }
  val += c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10;
  return val;
}

int WtDecodeUrlInPlace(char *str, int len)
{
  char *p = str;
  char *q = str;

  while (len--) {
    if (*p == '+') {
      *q++ = ' ';
      ++p;
    } else if (*p == '%' && len >= 2 && isxdigit(p[1]) &&
        isxdigit(p[2])) {
      *q = (char)WtHexToChar(p + 1);
      p += 3;
      len -= 2;
    } else {
      *q++ = *p++;
    }
  }

  *q = '\0';

  return q - str;
}
